package cn.hackcloud.utils;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;

public class ClassPathUpdater {
    /**
     * Used to find the method signature.
     */
    private static final Class[] PARAMETERS = new Class[]{URL.class};

    /**
     * Class containing the private addURL method.
     */
    private static final Class CLASS_LOADER = URLClassLoader.class;

    /**
     * Adds a new path to the classloader. If the given string points to a file,
     * then that file's parent file (i.e., directory) is used as the directory
     * to add to the classpath. If the given string represents a directory, then
     * the directory is directly added to the classpath.
     *
     * @param s The directory to add to the classpath (or a file, which will
     *          relegate to its directory).
     */
    public static void add(String s) throws IOException, NoSuchMethodException,
            IllegalAccessException, InvocationTargetException {
        add(new File(s));
    }

    /**
     * Adds a new path to the classloader. If the given file object is a file,
     * then its parent file (i.e., directory) is used as the directory to add to
     * the classpath. If the given string represents a directory, then the
     * directory it represents is added.
     *
     * @param f The directory (or enclosing directory if a file) to add to the
     *          classpath.
     */
    public static void add(File f) throws IOException, NoSuchMethodException,
            IllegalAccessException, InvocationTargetException {
        f = f.isDirectory() ? f : f.getParentFile();
        add(f.toURI().toURL());
    }

    /**
     * Adds a new path to the classloader. The class must point to a directory,
     * not a file.
     *
     * @param url The path to include when searching the classpath.
     */
    public static void add(URL url) throws IOException, NoSuchMethodException,
            IllegalAccessException, InvocationTargetException {
        Method method = CLASS_LOADER.getDeclaredMethod("addURL", PARAMETERS);
        method.setAccessible(true);
        method.invoke(getClassLoader(), new Object[]{url});
    }

    private static URLClassLoader getClassLoader() {
        return (URLClassLoader) ClassLoader.getSystemClassLoader();
    }

    static class MyClassLoader extends ClassLoader {
        @Override
        public Class<?> findClass(String name) {
            try {
                FileInputStream in = new FileInputStream(name);
                ArrayList<Byte> l = new ArrayList<>();
                int c;
                while ((c = in.read()) != -1) {
                    l.add(new Byte((byte) c));
                }
                byte[] b = new byte[l.size()];
                for (int i = 0; i < l.size(); i++) {
                    b[i] = l.get(i).byteValue();
                }

                return defineClass(name, b, 0, b.length);
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            return null;
        }
    }

    public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, IOException, ClassNotFoundException, InstantiationException {
//        ClassPathUpdater.add("E:\\workspaces\\MyDemos\\MyClass.class");
//        String path = "E:\\workspaces\\MyDemos\\";
//        URL url = new URL("file", null, path);
//        URLClassLoader loader = new URLClassLoader(new URL[]{url});
//        Class clazz = loader.loadClass("MyClass");
        MyClassLoader mcl = new MyClassLoader();
        Class clazz = mcl.loadClass("E:\\workspaces\\MyDemos\\MyClass.class");
        clazz.newInstance();
    }
}
